package engine;

import java.util.*;

import manager.PuzzleItem;
import manager.JumbleItem;
import manager.StandardWord;

public class JumbleGenerator implements Generator {
    char[][] letters = new char[2][];
    ArrayList<PuzzleItem> puzzleList = new ArrayList<PuzzleItem>();
    ArrayList<Pattern> pList;
    LinkedList<Integer> consJumble;
    LinkedList<Integer> vowelJumble;
    String original;
    
    public PuzzleItem generate(StandardWord standardWord) {
        pList = new ArrayList<Pattern>();
        original = standardWord.getWord();
        
        //Check word length, handle short words
        switch (standardWord.getWord().length()) {
            case 1: return new JumbleItem(standardWord,standardWord.getWord());
            case 2:
            case 3:
                return new JumbleItem(standardWord,
                                       randomString(standardWord.getWord()));
            default:
                break;
        }
        
        /*
        * Generate Jumble for each word provided by user.
        */
        int preType = -1;
        int count = 0;
        String word = standardWord.getWord();
   
   
        /*
        * Generate Pattern of the given word.
        */
        for(int i=0; i < word.length(); i++) {
            if((count < 3 && preType == vowelCheck(word.charAt(i))) ||
                preType == -1) {
                count++;
            } else {
                pList.add(new Pattern(preType, count));
                count = 1;
            }
            preType = vowelCheck(word.charAt(i));
        
        }
    
        pList.add(new Pattern(preType, count));
        
        int[] typeCount = countPatternTypes();
        
        if(typeCount[0] == 0 || typeCount[1] == 0) {
        	return new JumbleItem(standardWord,
                    randomString(standardWord.getWord()));
        }
    
        letters[Pattern.C] = getCons(word);
        letters[Pattern.V] = getVowels(word);
    
        LinkedList<Integer> patternJumble = indexJumble(pList.size());
        pList = jumblePatternList(patternJumble);
    
        consJumble = indexJumble(letters[0].length);
        vowelJumble = indexJumble(letters[1].length);
    
        String jumble = buildWord(word);
    
    
        return new JumbleItem(standardWord, jumble);
    }
    
    private boolean isOriginal(String t) {
    	if(t.equals(original)) return false;
    	return true;
    }
    
    private boolean isUnique(String t) {
    	ArrayList<Integer> v = new ArrayList<Integer>();
    	
    	for(int i = 0; i < t.length(); i++) {
    		int c = (int)t.charAt(i);
    		if(v.contains(c))
    			return false;
    		v.add(c);
    	}
    	
    	return true;
    }

    // Reverses a String
    private String randomString(String word) {
    	LinkedList<Integer> rnd = indexJumble(word.length());
        char[] buf = new char[word.length()];
    
        for (int i = 0; i < word.length(); i++) {
        	if(isUnique(word)) {
        		buf[i] = word.charAt(word.length()-i-1);
        	} else
        		buf[i] = word.charAt(rnd.get(i));
        }
        
        String ans = new String(buf);
        
        if(isOriginal(ans))
        	return ans;
        else
        	return randomString(word);
    }

    private int vowelCheck(char c) {
        switch(c) {
            case 'a':
            case 'e':
            case 'i':
            case 'o':
            case 'u':
            case 'A':
            case 'E':
            case 'I':
            case 'O':
            case 'U':
                return Pattern.V;
            default:
                return Pattern.C;
        }
    }

    private ArrayList<Pattern> jumblePatternList(LinkedList<Integer> patternJumble) {
        ArrayList<Pattern> newList = new ArrayList<Pattern>();
        while(true) {
            Integer idx = patternJumble.poll();
            Pattern p = pList.get(idx);
        
            newList.add(p);
        
            if(!patternJumble.isEmpty()) {
                if(check(newList))
                    continue;
            } else
                break;
        
        
            newList.remove(p);
            patternJumble.add(idx);
        }
    
        return newList;
    }

    private boolean check(ArrayList<Pattern> p) {
        int previousType = -1;
        int count = 0;
        int requiredCons;
        int cons = letters[0].length;
        int vowels = letters[1].length;
        Pattern t;
        int[] typeCount = countPatternTypes();
        
        if(typeCount[0] == 1 || typeCount[1] == 1) {
        	int startType = getStartType();
        	
        	if(p.get(0).type == startType)
        		return false;
        	else return true;
        	
        }
    
        for(int i = 0; i < p.size(); i++) {
        	if(vowels == 0)
        		requiredCons = cons;
        	else
        		requiredCons = cons/vowels;
        
            t = p.get(i);
        
            if(t.type == Pattern.V) {
                if(count < requiredCons)
                    return false;
            } else {
            	if(cons != 0)
            		if(requiredCons == 0) {
            			if(count < vowels/cons)
            				return false;
            		}
            }
        
        
            if(t.type != previousType) {
                previousType = t.type;
                count = 0;
            }
        
            switch(t.type) {
                case Pattern.C:
                    cons -= t.num;
                    break;
                case Pattern.V:
                    vowels -= t.num;
                    break;
            }
        
            count += t.num;
            if(count > 3) return false;
        }
    
        return true;
    }
    
    private int getStartType() {
    	return vowelCheck(original.charAt(0));
    }
    private int getEndType() {
    	return vowelCheck(original.charAt(original.length()-1));
    }
    
    private int[] countPatternTypes() {
    	int[] count = new int[2];
    	
    	for(Pattern p : pList)
    		count[p.type]++;
    	
    	return count;
    }

    /*
    Return all the constant from a String.
    */
    private char[] getCons(String word){
        String temp = "";
        for(int i=0; i<word.length(); i++)
            if(vowelCheck(word.charAt(i)) == Pattern.C)
                temp+=word.charAt(i);
            
            return temp.toCharArray();
        
    }
    
    /*
    Return all the vowel from a String.
    */
    private char[] getVowels(String word){
        String temp = "";
        for(int i=0; i<word.length(); i++)
            if(vowelCheck(word.charAt(i)) == Pattern.V)
                temp+=word.charAt(i);
            
            return temp.toCharArray();
        
    }
    
    /*
    Jumble an array of length. It initalize an array from zero to length
    and swap them around.
    */
        private LinkedList<Integer> indexJumble(int length){
            Random rand = new Random();
            LinkedList<Integer> tempArray = new LinkedList<Integer>();
            Integer temp;
            
            for(int i=0; i<length; i++)
                tempArray.add(i);
            
            
            for(int j=0;j<length; j++) {
                int index = rand.nextInt(length);
                temp = tempArray.get(index);
                tempArray.set(index, tempArray.get(j));
                tempArray.set(j, temp);
            }
            
            return tempArray;
        }
        
        private String buildWord(String original) {
            int idx;
            char[] forbidden = new char[2];
            
            forbidden[0] = original.charAt(0);
            forbidden[1] = original.charAt(original.length()-1);
            
            Pattern lastPattern = pList.get(pList.size()-1);
            
            String jumble = buildWord(forbidden[0], 0, pList.get(0));
            String end = buildWord(forbidden[1], lastPattern.num-1, lastPattern);
            
            for(int i = 1; i < pList.size()-1; i++) {
                jumble += buildWord(pList.get(i));
            }
            
            return jumble + end;
        }
        
        private String buildWord(char forbidden, int location, Pattern p) {
            Integer idx;
            LinkedList<Integer> list;
            LinkedList<Integer> usedIdx = new LinkedList<Integer>();
            int pattern;
            String ans = "";
            
            switch(p.type) {
                case Pattern.C:
                    list = consJumble;
                    pattern = Pattern.C;
                    break;
                case Pattern.V:
                    list = vowelJumble;
                    pattern = Pattern.V;
                    break;
                default:
                    System.out.println("That's not supposed to happen. Bad pattern in buildWord");
                    list = null;
                    pattern = -1;
            }
            
            for(int i = 0; i < p.num; i++) {
                idx = list.poll();
                if(i == location) {
                    if(forbidden == letters[pattern][idx]) {
                        list.add(idx);
                        while(!usedIdx.isEmpty())
                        	list.add(usedIdx.pollLast());
                        return buildWord(forbidden, location, p);
                    }
                }
                ans += letters[pattern][idx];
                usedIdx.add(idx);
            }
            return ans;
        }
        
        private String buildWord(Pattern p) {
            Integer idx;
            LinkedList<Integer> list;
            int pattern;
            String ans = "";
            
            switch(p.type) {
                case Pattern.C:
                    list = consJumble;
                    pattern = Pattern.C;
                    break;
                case Pattern.V:
                    list = vowelJumble;
                    pattern = Pattern.V;
                    break;
                default:
                    System.out.println("That's not supposed to happen. Bad pattern in buildWord");
                    list = null;
                    pattern = -1;
            }
            
            for(int i = 0; i < p.num; i++) {
                idx = list.poll();
                ans += letters[pattern][idx];
            }
            
            return ans;
        }
        
        
        public class Pattern {
            public static final int C = 0, V = 1;
            public int type;
            public int num;
            
            public Pattern(int t, int n) {
                type = t;
                num = n;
            }
        }
    }
        